/*! @azure/msal-common v13.3.1 2023-10-27 */
'use strict';
import { __assign } from '../../_virtual/_tslib.js';
import { IntFields, PerformanceEventStatus } from './PerformanceEvent.js';

/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
var PerformanceClient = /** @class */ (function () {
    /**
     * Creates an instance of PerformanceClient,
     * an abstract class containing core performance telemetry logic.
     *
     * @constructor
     * @param {string} clientId Client ID of the application
     * @param {string} authority Authority used by the application
     * @param {Logger} logger Logger used by the application
     * @param {string} libraryName Name of the library
     * @param {string} libraryVersion Version of the library
     */
    function PerformanceClient(clientId, authority, logger, libraryName, libraryVersion, applicationTelemetry) {
        this.authority = authority;
        this.libraryName = libraryName;
        this.libraryVersion = libraryVersion;
        this.applicationTelemetry = applicationTelemetry;
        this.clientId = clientId;
        this.logger = logger;
        this.callbacks = new Map();
        this.eventsByCorrelationId = new Map();
        this.queueMeasurements = new Map();
        this.preQueueTimeByCorrelationId = new Map();
    }
    /**
     * Starts and returns an platform-specific implementation of IPerformanceMeasurement.
     * Note: this function can be changed to abstract at the next major version bump.
     *
     * @param {string} measureName
     * @param {string} correlationId
     * @returns {IPerformanceMeasurement}
     */
    /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
    PerformanceClient.prototype.startPerformanceMeasurement = function (measureName, correlationId) {
        return {};
    };
    /**
     * Starts and returns an platform-specific implementation of IPerformanceMeasurement.
     * Note: this incorrectly-named function will be removed at the next major version bump.
     *
     * @param {string} measureName
     * @param {string} correlationId
     * @returns {IPerformanceMeasurement}
     */
    /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
    PerformanceClient.prototype.startPerformanceMeasuremeant = function (measureName, correlationId) {
        return {};
    };
    /**
     * Get integral fields.
     * Override to change the set.
     */
    PerformanceClient.prototype.getIntFields = function () {
        return IntFields;
    };
    /**
     * Gets map of pre-queue times by correlation Id
     *
     * @param {PerformanceEvents} eventName
     * @param {string} correlationId
     * @returns {number}
     */
    PerformanceClient.prototype.getPreQueueTime = function (eventName, correlationId) {
        var preQueueEvent = this.preQueueTimeByCorrelationId.get(correlationId);
        if (!preQueueEvent) {
            this.logger.trace("PerformanceClient.getPreQueueTime: no pre-queue times found for correlationId: " + correlationId + ", unable to add queue measurement");
            return;
        }
        else if (preQueueEvent.name !== eventName) {
            this.logger.trace("PerformanceClient.getPreQueueTime: no pre-queue time found for " + eventName + ", unable to add queue measurement");
            return;
        }
        return preQueueEvent.time;
    };
    /**
     * Calculates the difference between current time and time when function was queued.
     * Note: It is possible to have 0 as the queue time if the current time and the queued time was the same.
     *
     * @param {number} preQueueTime
     * @param {number} currentTime
     * @returns {number}
     */
    PerformanceClient.prototype.calculateQueuedTime = function (preQueueTime, currentTime) {
        if (preQueueTime < 1) {
            this.logger.trace("PerformanceClient: preQueueTime should be a positive integer and not " + preQueueTime);
            return 0;
        }
        if (currentTime < 1) {
            this.logger.trace("PerformanceClient: currentTime should be a positive integer and not " + currentTime);
            return 0;
        }
        if (currentTime < preQueueTime) {
            this.logger.trace("PerformanceClient: currentTime is less than preQueueTime, check how time is being retrieved");
            return 0;
        }
        return currentTime - preQueueTime;
    };
    /**
     * Adds queue measurement time to QueueMeasurements array for given correlation ID.
     *
     * @param {PerformanceEvents} eventName
     * @param {?string} correlationId
     * @param {?number} queueTime
     * @param {?boolean} manuallyCompleted - indicator for manually completed queue measurements
     * @returns
     */
    PerformanceClient.prototype.addQueueMeasurement = function (eventName, correlationId, queueTime, manuallyCompleted) {
        if (!correlationId) {
            this.logger.trace("PerformanceClient.addQueueMeasurement: correlationId not provided for " + eventName + ", cannot add queue measurement");
            return;
        }
        if (queueTime === 0) {
            // Possible for there to be no queue time after calculation
            this.logger.trace("PerformanceClient.addQueueMeasurement: queue time provided for " + eventName + " is " + queueTime);
        }
        else if (!queueTime) {
            this.logger.trace("PerformanceClient.addQueueMeasurement: no queue time provided for " + eventName);
            return;
        }
        var queueMeasurement = { eventName: eventName, queueTime: queueTime, manuallyCompleted: manuallyCompleted };
        // Adds to existing correlation Id if present in queueMeasurements
        var existingMeasurements = this.queueMeasurements.get(correlationId);
        if (existingMeasurements) {
            existingMeasurements.push(queueMeasurement);
            this.queueMeasurements.set(correlationId, existingMeasurements);
        }
        else {
            // Sets new correlation Id if not present in queueMeasurements
            this.logger.trace("PerformanceClient.addQueueMeasurement: adding correlationId " + correlationId + " to queue measurements");
            var measurementArray = [queueMeasurement];
            this.queueMeasurements.set(correlationId, measurementArray);
        }
        // Delete processed pre-queue event.
        this.preQueueTimeByCorrelationId.delete(correlationId);
    };
    /**
     * Starts measuring performance for a given operation. Returns a function that should be used to end the measurement.
     *
     * @param {PerformanceEvents} measureName
     * @param {?string} [correlationId]
     * @returns {InProgressPerformanceEvent}
     */
    PerformanceClient.prototype.startMeasurement = function (measureName, correlationId) {
        var _this = this;
        var _a, _b;
        // Generate a placeholder correlation if the request does not provide one
        var eventCorrelationId = correlationId || this.generateId();
        if (!correlationId) {
            this.logger.info("PerformanceClient: No correlation id provided for " + measureName + ", generating", eventCorrelationId);
        }
        // Duplicate code to address spelling error will be removed at the next major version bump.
        this.logger.trace("PerformanceClient: Performance measurement started for " + measureName, eventCorrelationId);
        var performanceMeasurement = this.startPerformanceMeasuremeant(measureName, eventCorrelationId);
        performanceMeasurement.startMeasurement();
        var inProgressEvent = {
            eventId: this.generateId(),
            status: PerformanceEventStatus.InProgress,
            authority: this.authority,
            libraryName: this.libraryName,
            libraryVersion: this.libraryVersion,
            clientId: this.clientId,
            name: measureName,
            startTimeMs: Date.now(),
            correlationId: eventCorrelationId,
            appName: (_a = this.applicationTelemetry) === null || _a === void 0 ? void 0 : _a.appName,
            appVersion: (_b = this.applicationTelemetry) === null || _b === void 0 ? void 0 : _b.appVersion,
        };
        // Store in progress events so they can be discarded if not ended properly
        this.cacheEventByCorrelationId(inProgressEvent);
        // Return the event and functions the caller can use to properly end/flush the measurement
        return {
            endMeasurement: function (event) {
                return _this.endMeasurement(__assign(__assign({}, inProgressEvent), event), performanceMeasurement);
            },
            discardMeasurement: function () {
                return _this.discardMeasurements(inProgressEvent.correlationId);
            },
            addStaticFields: function (fields) {
                return _this.addStaticFields(fields, inProgressEvent.correlationId);
            },
            increment: function (counters) {
                return _this.increment(counters, inProgressEvent.correlationId);
            },
            measurement: performanceMeasurement,
            event: inProgressEvent
        };
    };
    /**
     * Stops measuring the performance for an operation. Should only be called directly by PerformanceClient classes,
     * as consumers should instead use the function returned by startMeasurement.
     * Adds a new field named as "[event name]DurationMs" for sub-measurements, completes and emits an event
     * otherwise.
     *
     * @param {PerformanceEvent} event
     * @param {IPerformanceMeasurement} measurement
     * @returns {(PerformanceEvent | null)}
     */
    PerformanceClient.prototype.endMeasurement = function (event, measurement) {
        var _this = this;
        var _a, _b;
        var rootEvent = this.eventsByCorrelationId.get(event.correlationId);
        if (!rootEvent) {
            this.logger.trace("PerformanceClient: Measurement not found for " + event.eventId, event.correlationId);
            return null;
        }
        var isRoot = event.eventId === rootEvent.eventId;
        var queueInfo = {
            totalQueueTime: 0,
            totalQueueCount: 0,
            manuallyCompletedCount: 0
        };
        if (isRoot) {
            queueInfo = this.getQueueInfo(event.correlationId);
            this.discardCache(rootEvent.correlationId);
        }
        else {
            (_a = rootEvent.incompleteSubMeasurements) === null || _a === void 0 ? void 0 : _a.delete(event.eventId);
        }
        measurement === null || measurement === void 0 ? void 0 : measurement.endMeasurement();
        var durationMs = measurement === null || measurement === void 0 ? void 0 : measurement.flushMeasurement();
        // null indicates no measurement was taken (e.g. needed performance APIs not present)
        if (!durationMs) {
            this.logger.trace("PerformanceClient: Performance measurement not taken", rootEvent.correlationId);
            return null;
        }
        this.logger.trace("PerformanceClient: Performance measurement ended for " + event.name + ": " + durationMs + " ms", event.correlationId);
        // Add sub-measurement attribute to root event.
        if (!isRoot) {
            rootEvent[event.name + "DurationMs"] = Math.floor(durationMs);
            return __assign({}, rootEvent);
        }
        var finalEvent = __assign(__assign({}, rootEvent), event);
        var incompleteSubsCount = 0;
        // Incomplete sub-measurements are discarded. They are likely an instrumentation bug that should be fixed.
        (_b = finalEvent.incompleteSubMeasurements) === null || _b === void 0 ? void 0 : _b.forEach(function (subMeasurement) {
            _this.logger.trace("PerformanceClient: Incomplete submeasurement " + subMeasurement.name + " found for " + event.name, finalEvent.correlationId);
            incompleteSubsCount++;
        });
        finalEvent.incompleteSubMeasurements = undefined;
        finalEvent = __assign(__assign({}, finalEvent), { durationMs: Math.round(durationMs), queuedTimeMs: queueInfo.totalQueueTime, queuedCount: queueInfo.totalQueueCount, queuedManuallyCompletedCount: queueInfo.manuallyCompletedCount, status: PerformanceEventStatus.Completed, incompleteSubsCount: incompleteSubsCount });
        this.truncateIntegralFields(finalEvent, this.getIntFields());
        this.emitEvents([finalEvent], event.correlationId);
        return finalEvent;
    };
    /**
     * Saves extra information to be emitted when the measurements are flushed
     * @param fields
     * @param correlationId
     */
    PerformanceClient.prototype.addStaticFields = function (fields, correlationId) {
        this.logger.trace("PerformanceClient: Updating static fields");
        var event = this.eventsByCorrelationId.get(correlationId);
        if (event) {
            this.eventsByCorrelationId.set(correlationId, __assign(__assign({}, event), fields));
        }
        else {
            this.logger.trace("PerformanceClient: Event not found for", correlationId);
        }
    };
    /**
     * Increment counters to be emitted when the measurements are flushed
     * @param counters {Counters}
     * @param correlationId {string} correlation identifier
     */
    PerformanceClient.prototype.increment = function (counters, correlationId) {
        this.logger.trace("PerformanceClient: Updating counters");
        var event = this.eventsByCorrelationId.get(correlationId);
        if (event) {
            for (var counter in counters) {
                if (!event.hasOwnProperty(counter)) {
                    event[counter] = 0;
                }
                event[counter] += counters[counter];
            }
        }
        else {
            this.logger.trace("PerformanceClient: Event not found for", correlationId);
        }
    };
    /**
     * Upserts event into event cache.
     * First key is the correlation id, second key is the event id.
     * Allows for events to be grouped by correlation id,
     * and to easily allow for properties on them to be updated.
     *
     * @private
     * @param {PerformanceEvent} event
     */
    PerformanceClient.prototype.cacheEventByCorrelationId = function (event) {
        var rootEvent = this.eventsByCorrelationId.get(event.correlationId);
        if (rootEvent) {
            this.logger.trace("PerformanceClient: Performance measurement for " + event.name + " added/updated", event.correlationId);
            rootEvent.incompleteSubMeasurements = rootEvent.incompleteSubMeasurements || new Map();
            rootEvent.incompleteSubMeasurements.set(event.eventId, { name: event.name, startTimeMs: event.startTimeMs });
        }
        else {
            this.logger.trace("PerformanceClient: Performance measurement for " + event.name + " started", event.correlationId);
            this.eventsByCorrelationId.set(event.correlationId, __assign({}, event));
        }
    };
    PerformanceClient.prototype.getQueueInfo = function (correlationId) {
        var queueMeasurementForCorrelationId = this.queueMeasurements.get(correlationId);
        if (!queueMeasurementForCorrelationId) {
            this.logger.trace("PerformanceClient: no queue measurements found for for correlationId: " + correlationId);
        }
        var totalQueueTime = 0;
        var totalQueueCount = 0;
        var manuallyCompletedCount = 0;
        queueMeasurementForCorrelationId === null || queueMeasurementForCorrelationId === void 0 ? void 0 : queueMeasurementForCorrelationId.forEach(function (measurement) {
            totalQueueTime += measurement.queueTime;
            totalQueueCount++;
            manuallyCompletedCount += measurement.manuallyCompleted ? 1 : 0;
        });
        return {
            totalQueueTime: totalQueueTime,
            totalQueueCount: totalQueueCount,
            manuallyCompletedCount: manuallyCompletedCount
        };
    };
    /**
     * Removes measurements for a given correlation id.
     *
     * @param {string} correlationId
     */
    PerformanceClient.prototype.discardMeasurements = function (correlationId) {
        this.logger.trace("PerformanceClient: Performance measurements discarded", correlationId);
        this.eventsByCorrelationId.delete(correlationId);
    };
    /**
     * Removes cache for a given correlation id.
     *
     * @param {string} correlationId correlation identifier
     */
    PerformanceClient.prototype.discardCache = function (correlationId) {
        this.discardMeasurements(correlationId);
        this.logger.trace("PerformanceClient: QueueMeasurements discarded", correlationId);
        this.queueMeasurements.delete(correlationId);
        this.logger.trace("PerformanceClient: Pre-queue times discarded", correlationId);
        this.preQueueTimeByCorrelationId.delete(correlationId);
    };
    /**
     * Registers a callback function to receive performance events.
     *
     * @param {PerformanceCallbackFunction} callback
     * @returns {string}
     */
    PerformanceClient.prototype.addPerformanceCallback = function (callback) {
        var callbackId = this.generateId();
        this.callbacks.set(callbackId, callback);
        this.logger.verbose("PerformanceClient: Performance callback registered with id: " + callbackId);
        return callbackId;
    };
    /**
     * Removes a callback registered with addPerformanceCallback.
     *
     * @param {string} callbackId
     * @returns {boolean}
     */
    PerformanceClient.prototype.removePerformanceCallback = function (callbackId) {
        var result = this.callbacks.delete(callbackId);
        if (result) {
            this.logger.verbose("PerformanceClient: Performance callback " + callbackId + " removed.");
        }
        else {
            this.logger.verbose("PerformanceClient: Performance callback " + callbackId + " not removed.");
        }
        return result;
    };
    /**
     * Emits events to all registered callbacks.
     *
     * @param {PerformanceEvent[]} events
     * @param {?string} [correlationId]
     */
    PerformanceClient.prototype.emitEvents = function (events, correlationId) {
        var _this = this;
        this.logger.verbose("PerformanceClient: Emitting performance events", correlationId);
        this.callbacks.forEach(function (callback, callbackId) {
            _this.logger.trace("PerformanceClient: Emitting event to callback " + callbackId, correlationId);
            callback.apply(null, [events]);
        });
    };
    /**
     * Enforce truncation of integral fields in performance event.
     * @param {PerformanceEvent} event performance event to update.
     * @param {Set<string>} intFields integral fields.
     */
    PerformanceClient.prototype.truncateIntegralFields = function (event, intFields) {
        intFields.forEach(function (key) {
            if (key in event && typeof event[key] === "number") {
                event[key] = Math.floor(event[key]);
            }
        });
    };
    return PerformanceClient;
}());

export { PerformanceClient };
//# sourceMappingURL=PerformanceClient.js.map
